import json
import sys
import time
import socket

from PyQt5 import QtCore
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtWidgets import QMessageBox, QAbstractItemView
from PyQt5.Qt import QThread

import numpy as np

import window_main
from Robotics import Robot

# ================ 全局变量定义 ================#
clientsockets = []  # 保存客户端socket
client_addrs = []  # 保存客户端addr
Robot_list = []  # 保存智能体对象
connect_num = 0  # 客户端连接数量
connect_change = False  # 客户端数量变化标志
Thread_recv_send_list = []  # 客户端收发数据线程列表
# 发送数据缓冲区
Send_data = {
    "header": "",
    "start_x": 0.0,
    "start_y": 0.0
}


# ==========================================================================#
#                             客户端通信线程
# 功能：客户端通信
# ==========================================================================#
class Thread_Recv_Send(QThread):
    def __init__(self, clientsocket, addr, data_signal):
        super().__init__()
        self.clientsocket = clientsocket
        self.data_signal = data_signal
        self.addr = addr

        self.recved = None

    def run(self):  # 重写run方法
        global connect_change
        global connect_num
        while True:
            self.recved = self.clientsocket.recv(256)
            if self.recved:
                # 读取的字节数据是bytes类型，需要解码为字符串
                info = self.recved.decode()
                # 找到addr在clinet_addrs中的索引,判断智能体的ID
                index = client_addrs.index(self.addr)
                print('第', index + 1, '个智能体发来消息:', end='')
                self.data_signal.emit(str(index + 1) + ':' + info)

                # 发送的数据类型必须是bytes，所以要编码
                # self.clientsocket.send(f'{info}'.encode())
            else:
                print('客户端', self.addr, '退出')
                # 找到addr在clinet_addrs中的索引
                index = client_addrs.index(self.addr)
                # 按照索引删除列表中的元素
                client_addrs.pop(index)  # 删除客户端的addr
                clientsockets.pop(index)  # 删除客户端的socket
                Robot_list.pop(index)  # 删除智能体列表中的相应Robot对象
                Thread_recv_send_list.pop(index)
                connect_num = connect_num - 1
                connect_change = True
                break


# ==========================================================================#
#                              TCP服务器监听线程
# 功能：监听客户端连接
#      创建客户端通信线程
#      关闭客户端通信线程
#      清除客户端连接列表等
# ==========================================================================#
class Server_Thread(QThread):
    def __init__(self, serverSocket, data_signal):
        super().__init__()

        print('2.TCP服务器线程接收到socket')

        self.run_flag = True
        self.data_signal = data_signal
        self.serverSocket = serverSocket
        # self.close_server_signal.connect(self.close_server)

    def run(self):
        global connect_num
        global connect_change
        print('3.等待客户端连接......')
        while True:
            # ================ 监听客户端连接 ================#
            # 收到客户端的连接后返回 连接控件,地址(持续监听,直到接收到执行下一个操作)
            self.clientsocket, self.addr = self.serverSocket.accept()
            # 连接数加一
            connect_num = connect_num + 1
            connect_change = True
            print('接受第', connect_num, '个新客户端连接:', self.addr)
            # 添加客户端addr
            client_addrs.append(self.addr[1])
            # 添加clientsocket
            clientsockets.append(self.clientsocket)
            # 添加Robot对象
            Robot_list.append(Robot())
            # 创建新的客户端通信子线程，传入clientsocket、addr、断开连接信号
            self.thread_recv_send = Thread_Recv_Send(self.clientsocket, self.addr[1], self.data_signal)
            self.thread_recv_send.start()
            Thread_recv_send_list.append(self.thread_recv_send)


# ==========================================================================#
#                                   子线程
# 功能：创建socket套接字
#      创建服务器线程（监听客户端连接）
#      关闭服务器
#      显示当前客户端数量
# ==========================================================================#
class Thread(QThread):
    open_server = pyqtSignal(str)
    close_server = pyqtSignal()
    data_signal = pyqtSignal(str)

    def __init__(self, mainui):
        super().__init__()
        self.mainui = mainui
        self.socket_class = None
        self.server = None
        self.open_server.connect(self.Open_TcpServer)
        self.close_server.connect(self.Close_TcpServer)
        self.data_signal.connect(self.Data_Handle)

    # 处理客户端数据
    def Data_Handle(self, client_data):
        print(f'{client_data}')

    # 创建TCP服务器，使用socket套接字
    def Open_TcpServer(self, server_options):
        # 将json字符串转换为自定（之前的类型格式，这里是字典）
        server_options = json.loads(server_options)
        ip = server_options.get("ip")
        port = server_options.get("port")
        listen_max = server_options.get("listen_max")
        print('1.子线程收到信号:', server_options)
        # ================ 创建socket套接字 ================#
        # 创建socket
        # AF_INET:表示使用IP协议，SOCK_STREAM:表示使用TCP协议
        self.listenSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.listenSocket.bind((str(ip), int(port)))
        self.listenSocket.listen(int(listen_max))

        # ================ 创建服务器线程（客户端连接） ================#
        self.server = Server_Thread(self.listenSocket, self.data_signal)
        self.server.start()

    # 关闭TCP服务器
    def Close_TcpServer(self):
        global clientsockets
        global Thread_recv_send_list
        global clientsockets
        global client_addrs
        global Robot_list
        global connect_num
        global connect_change
        print('收到关闭服务器命令')
        # 循环列表，关闭socket和数据收发线程
        for i in range(len(client_addrs)):
            clientsockets[i].close()
            Thread_recv_send_list[i].terminate()  # 关闭客户端数据收发线程
        clientsockets.clear()
        Thread_recv_send_list.clear()
        clientsockets.clear()
        client_addrs.clear()
        connect_num = 0
        connect_change = True
        print('服务器关闭')
        self.listenSocket.close()  # 关闭socket对象
        self.server.terminate()  # 强制退出服务器线程

    def run(self):
        global connect_change
        print('子线程正在运行......')
        while True:
            # 当客户端数量变化时改变label_4显示内容
            if connect_change:
                connect_change = False
                self.mainui.label_4.setText(str(connect_num))
            time.sleep(0.05)


# ==========================================================================#
#                          UI 类，继承Ui_MainWindow
# 功能：显示UI
#      定义按键信号
#      打开服务器
#      设置智能体起始位置
# ==========================================================================#
class MyWindow(window_main.Ui_MainWindow):
    def __init__(self, MainWindow):
        super().setupUi(MainWindow)

        # 声明信号
        self.signal_fun()

        # ================ 开启服务器子线程（创建socket套接字） ================#
        self.thread = Thread(self)
        self.thread.start()

    # ================ 信号 ================#
    def signal_fun(self):
        # 1.初始位置设置按键信号
        self.start_pushButton.clicked.connect(self.start_pushButton_clicked)
        # 2.TCP服务器按键信号
        self.server_pushButton.clicked.connect(self.server_pushButton_clicked)

    # ================ 槽函数 ================#
    # 1.智能体初始位置设置按键
    def start_pushButton_clicked(self):
        global connect_num
        global Send_data
        if self.start_pushButton.text() == '确定':
            res = QMessageBox.information(None, "", "确定起始位置已设置完成？", QMessageBox.Yes | QMessageBox.No)
            if res == QMessageBox.Yes:
                # ---- 1.遍历tableWidget的所有行，获取Robots的初始位置 ---- #
                xy_array = np.empty(shape=[0, 2])  # 保存xy坐标
                for i in range(self.tableWidget.model().rowCount()):
                    # 没有被编辑前的空 && 被编辑后又删除后的空
                    if self.tableWidget.item(i, 0) and self.tableWidget.item(i, 0).text() and self.tableWidget.item(i,
                                                                                                                    1) and self.tableWidget.item(
                            i, 1).text():
                        xy_array = np.append(xy_array, [float(self.tableWidget.item(i, 0).text()),
                                                        float(self.tableWidget.item(i, 1).text())])
                    else:
                        break
                xy_array = xy_array.reshape(int(xy_array.shape[0] / 2), 2)  # 修改数组形状，改成n行两列
                print('初始位置数组:\n', xy_array)

                # ---- 2.当tablewidget中的行数等于智能体个数且大于0时，才允许设置Robots的初始位置 ---- #
                if xy_array.shape[0] > 0 and xy_array.shape[0] == connect_num:
                    for i in range(xy_array.shape[0]):
                        Robot_list[i].start_x = xy_array[i][0]
                        Robot_list[i].current_x = xy_array[i][0]

                        Robot_list[i].start_y = xy_array[i][1]
                        Robot_list[i].current_y = xy_array[i][1]
                    # 将初始位置发送给每个智能体(Json数据)
                    for i in range(connect_num):
                        Send_data["header"] = "start"
                        Send_data["start_x"] = Robot_list[i].start_x
                        Send_data["start_y"] = Robot_list[i].start_y
                        clientsockets[i].send(json.dumps(Send_data).encode())
                        # 输出智能体初始位置
                        print(i + 1, ': start_x: ', Robot_list[i].start_x, '  start_y: ', Robot_list[i].start_y)

                    print('设置起始坐标成功')
                    # tableWidget不可编辑
                    self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
                    self.start_pushButton.setText('取消')
                else:
                    QMessageBox.information(None, "", "位置数量与智能体个数不符", QMessageBox.Ok)
        else:
            if self.start_pushButton.text() == '取消':
                res = QMessageBox.information(None, "", "确定重新设置起始位置吗？", QMessageBox.Yes | QMessageBox.No)
                if res == QMessageBox.Yes:
                    print('取消起始位置')
                    self.start_pushButton.setText('确定')
                    # 双击可编辑(也可以CurrentChanged、SelectedClicked)
                    self.tableWidget.setEditTriggers(QAbstractItemView.DoubleClicked)

    # 2.打开TCP服务器按键函数
    def server_pushButton_clicked(self):
        if self.server_pushButton.text() == '打开':
            ip = self.comboBox.currentText()
            port = self.lineEdit_2.text()
            listen_max = self.lineEdit_3.text()
            self.thread.open_server.emit(json.dumps({"ip": ip, "port": port, "listen_max": listen_max}))
            self.server_pushButton.setText('关闭')
        else:
            self.thread.close_server.emit()
            self.server_pushButton.setText('打开')


# 192.168.31.145
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = QMainWindow()
    myui = MyWindow(window)
    window.show()
    app.exec()
